// Copyright © 2011-12 Qtrac Ltd.
// 
// This program or package and any associated files are licensed under the
// Apache License, Version 2.0 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.apache.org/licenses/LICENSE-2.0.
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
    "encoding/xml"
    "fmt"
    "io"
    "strings"
    "time"
)

type XMLMarshaler struct{}

type XMLInvoices struct {
    XMLName xml.Name      `xml:"INVOICES"`
    Version int           `xml:"version,attr"`
    Invoice []*XMLInvoice `xml:"INVOICE"`
}

type XMLInvoice struct {
    XMLName      xml.Name   `xml:"INVOICE"`
    Id           int        `xml:",attr"`
    CustomerId   int        `xml:",attr"`
    DepartmentId string     `xml:",attr"`
    Raised       string     `xml:",attr"`
    Due          string     `xml:",attr"`
    Paid         bool       `xml:",attr"`
    Note         string     `xml:"NOTE"`
    Item         []*XMLItem `xml:"ITEM"`
}

type XMLItem struct {
    XMLName  xml.Name `xml:"ITEM"`
    Id       string   `xml:",attr"`
    Price    float64  `xml:",attr"`
    Quantity int      `xml:",attr"`
    TaxBand  int      `xml:",attr"`
    Note     string   `xml:"NOTE"`
}


func XMLInvoicesForInvoices(invoices []*Invoice) *XMLInvoices {
    xmlInvoices := &XMLInvoices{
        Version: fileVersion,
        Invoice: make([]*XMLInvoice, 0, len(invoices)),
    }
    for _, invoice := range invoices {
        xmlInvoices.Invoice = append(xmlInvoices.Invoice,
            XMLInvoiceForInvoice(invoice))
    }
    return xmlInvoices
}


func XMLInvoiceForInvoice(invoice *Invoice) *XMLInvoice {
    xmlInvoice := &XMLInvoice{
        Id:           invoice.Id,
        CustomerId:   invoice.CustomerId,
        DepartmentId: invoice.DepartmentId,
        Raised:       invoice.Raised.Format(dateFormat),
        Due:          invoice.Due.Format(dateFormat),
        Paid:         invoice.Paid,
        Note:         invoice.Note,
        Item:         make([]*XMLItem, 0, len(invoice.Items)),
    }
    for _, item := range invoice.Items {
        xmlItem := &XMLItem{
            Id:       item.Id,
            Price:    item.Price,
            Quantity: item.Quantity,
            TaxBand:  item.TaxBand,
            Note:     item.Note,
        }
        xmlInvoice.Item = append(xmlInvoice.Item, xmlItem)
    }
    return xmlInvoice
}


func (xmlInvoices *XMLInvoices) Invoices(version int) (invoices []*Invoice,
    err error) {
    invoices = make([]*Invoice, 0, len(xmlInvoices.Invoice))
    for _, xmlInvoice := range xmlInvoices.Invoice {
        invoice, err := xmlInvoice.Invoice(version)
        if err != nil {
            return nil, err
        }
        invoices = append(invoices, invoice)
    }
    return invoices, nil
}


func (xmlInvoice *XMLInvoice) Invoice(version int) (invoice *Invoice,
    err error) {
    invoice = &Invoice{
        Id:         xmlInvoice.Id,
        CustomerId: xmlInvoice.CustomerId,
        Paid:       xmlInvoice.Paid,
        Note:       strings.TrimSpace(xmlInvoice.Note),
        Items:      make([]*Item, 0, len(xmlInvoice.Item)),
    }
    if version == fileVersion {
        invoice.DepartmentId = xmlInvoice.DepartmentId
    }
    if invoice.Raised, err = time.Parse(dateFormat, xmlInvoice.Raised);
        err != nil {
        return nil, err
    }
    if invoice.Due, err = time.Parse(dateFormat, xmlInvoice.Due);
        err != nil {
        return nil, err
    }
    if invoice.Items, err = itemsForXMLItems(version, xmlInvoice.Item);
        err != nil {
        return nil, err
    }
    if version < fileVersion {
        updateInvoice(invoice)
    }
    return invoice, nil
}

func itemsForXMLItems(version int, xmlItems []*XMLItem) (items []*Item,
    err error) {
    for _, xmlItem := range xmlItems {
        item := &Item{
            Id:       xmlItem.Id,
            Price:    xmlItem.Price,
            Quantity: xmlItem.Quantity,
            Note:     strings.TrimSpace(xmlItem.Note),
        }
        if version == fileVersion {
            item.TaxBand = xmlItem.TaxBand
        } else if version < fileVersion {
            if err = updateItem(item); err != nil {
                return nil, err
            }
        }
        items = append(items, item)
    }
    return items, nil
}


func (XMLMarshaler) MarshalInvoices(writer io.Writer,
    invoices []*Invoice) error {
    if _, err := writer.Write([]byte(xml.Header)); err != nil {
        return err
    }
    xmlInvoices := XMLInvoicesForInvoices(invoices)
    encoder := xml.NewEncoder(writer)
    return encoder.Encode(xmlInvoices)
}


func (XMLMarshaler) UnmarshalInvoices(reader io.Reader) ([]*Invoice,
    error) {
    xmlInvoices := &XMLInvoices{}
    decoder := xml.NewDecoder(reader)
    if err := decoder.Decode(xmlInvoices); err != nil {
        return nil, err
    }
    if xmlInvoices.Version > fileVersion {
        return nil, fmt.Errorf("version %d is too new to read",
            xmlInvoices.Version)
    }
    return xmlInvoices.Invoices(xmlInvoices.Version)
}
